﻿// using System.IO;
using System.Buffers.Text;
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading.Tasks;
using UMC.SshNet;
using UMC.Net;
using System.Text;

namespace UMC.ITME
{

    abstract class HttpMime : IDisposable
    {
        public virtual object Tag => null;
        public abstract int Id { get; }
        public uint ActiveTime
        {
            get; set;
        }
        public bool? IsPass
        {
            get; set;
        }
        ByteChunk mimeMemory = new ByteChunk(0x1000000);
        public ByteChunk MimeMemory => mimeMemory;
        public int TimeOut { protected set; get; } = 20;
        public virtual bool IsSSL => false;

        public void OutputFinish()
        {

            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            this.TimeOut = 20;
            this.Request = null;

        }
        public bool IsWrited { set; get; } = false;

        protected abstract void Send(byte[] buffer, int offset, int count);
        public virtual void Write(byte[] buffer, int offset, int count)
        {
            IsWrited = true;
            this.Send(buffer, offset, count);
        }
        public void Challenge(StringValue host, StringValue challengeValue)
        {

            var writer = new UMC.Net.TextWriter(this.Send);
            writer.Write($"HTTP/1.1 200 {HttpStatusDescription.Get(200)}\r\n");
            writer.Write($"Content-Type: text/html; charset=utf-8\r\n");
            writer.Write("Connection: close\r\n");
            writer.Write($"Content-Length: 0\r\n");
            var cdmn = WebServlet.MainDomainValue;
            var cdm = cdmn;

            if (WebServlet.DomainUnion == '-')
            {
                cdm = cdmn.Slice(cdmn.Span.IndexOf((byte)'.'));
            }
            if (host.Span.EndsWith(cdm.Span))
            {
                writer.Write($"Set-Cookie: {HttpsMimeRequest.authKey}={challengeValue}; Expires={DateTime.Now.ToUniversalTime().AddYears(1):r}; HttpOnly; SameSite=Lax; Domain={cdm}; Path=/\r\n");
            }
            else
            {
                writer.Write($"Set-Cookie: {HttpsMimeRequest.authKey}={challengeValue}; Expires={DateTime.Now.ToUniversalTime().AddYears(1):r}; HttpOnly; SameSite=Lax; Path=/\r\n");

            }
            writer.Write("Server: ITME\r\n\r\n");
            writer.Flush();
            writer.Close();
            if (this.Request is HttpMimeRequest)
            {
                var h = this.Request as HttpMimeRequest;
                h.netLoger.Write(HttpLogerType.Status, 200);
                HttpLogerDoWorker.AppendLog(h.netLoger);
            }
            this.Dispose();
        }


        public abstract StringValue Finger { get; }
        public abstract void Dispose();
        public abstract StringValue Host { get; }
        public abstract StringValue RemoteIpAddress { get; }


        public Mime Request;

        protected void WebSocket(NetContext context)
        {
            if (context.Tag is NetHttpRequest)
            {
                var webr = context.Tag as NetHttpRequest;
                this.WebSocket(webr);
            }
            else
            {
                OutText(403, "WebSocket");
            }
        }
        public virtual void Stop()
        {
            this.Dispose();
        }
        async void WebSocket(NetHttpRequest webRequest)
        {
            var url = webRequest.Address;

            webRequest.Headers[StringValue.Connection] = new StringValue("Upgrade".UTF8());
            try
            {
                var client = await NetProxy.Connect(url.Host, url.Port, 0);

                byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);

                try
                {
                    if (url.Scheme == "https")
                    {
                        SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
                        await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);
                        var textWriter = new TextWriter(ssl.Write, _data);

                        UMC.Net.NetHttpResponse.Header(webRequest, textWriter.Write);
                        textWriter.Flush();

                        textWriter.Dispose();

                        int size = await ssl.ReadAsync(_data, 0, _data.Length);

                        if (NetBridge.ResponseHeader(_data, 0, size, new HeadValues(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                        {
                            this.Request = new HttpsWebSocket(this, client, ssl);
                            this.Send(_data, 0, size);
                        }
                        else
                        {
                            this.Send(_data, 0, size);
                            ssl.Dispose();
                            this.Dispose();
                        }
                    }
                    else
                    {
                        var textWriter = new TextWriter((b, c, s) =>
                        {
                            client.Send(b, c, s, SocketFlags.None);
                        }, _data);
                        UMC.Net.NetHttpResponse.Header(webRequest, textWriter.Write);
                        textWriter.Flush();
                        textWriter.Dispose();

                        var size = client.Receive(_data, 0, _data.Length, SocketFlags.None);
                        if (NetBridge.ResponseHeader(_data, 0, size, new HeadValues(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
                        {
                            this.Request = new HttpWebSocket(this, client);
                            this.Send(_data, 0, size);
                        }
                        else
                        {

                            this.Send(_data, 0, size);
                            this.Dispose();
                        }

                    }
                }
                catch (Exception ex)
                {
                    this.OutText(500, ex.ToString());
                }
                finally
                {
                    System.Buffers.ArrayPool<byte>.Shared.Return(_data);
                }

            }
            catch (Exception ex)
            {
                this.OutText(500, ex.ToString());
            }


            HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
        }
        public virtual void PrepareRespone(HttpMimeRequest request)
        {

            this.TimeOut = 300;
            try
            {
                if (request.IsWebSocket)
                {
                    if (request.Path.Span.StartsWith("/UMC.WS/"u8))
                    {
                        var Path = request.Path.Span.Slice(8).UTF8();
                        Guid dID;
                        var sttDevice = Path;
                        if (Path.Contains("/"))
                        {
                            sttDevice = request.Cookies.Get("device"u8).Span.UTF8();
                            dID = SiteConfig.MD5Key(sttDevice, Path);
                        }
                        else
                        {
                            dID = UMC.Data.Utility.Guid(Path, true).Value;
                        }

                        var secWebSocketKey = request.Headers.Get("Sec-WebSocket-Key"u8);
                        if (secWebSocketKey.IsEmpty == false)
                        {
                            var buffers = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);

                            var webr = new UMC.Host.HttpWebSocket(this.Write, dID, this.Dispose, request.Memory);
                            this.Request = webr;
                            secWebSocketKey.Span.CopyTo(buffers);
                            var size = secWebSocketKey.Span.Length;
                            size += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".WriteBytes(buffers, size);
                            int len = System.Security.Cryptography.SHA1.HashData(buffers.AsSpan(0, size), buffers.AsSpan(size, 24));
                            string secWebSocketAcceptString = Convert.ToBase64String(buffers.AsSpan(size, len));
                            var writer = new Net.TextWriter(request._context.Write, buffers);
                            writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
                            writer.Write("Connection: Upgrade\r\n");
                            writer.Write("Upgrade: websocket\r\n");
                            writer.Write($"Sec-WebSocket-Accept: {secWebSocketAcceptString}\r\n");
                            writer.Write("Server: ITME\r\n\r\n");
                            writer.Flush();
                            writer.Dispose();
                            System.Buffers.ArrayPool<byte>.Shared.Return(buffers);
                            HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
                            var devices = Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
                            if (devices.Length > 1)
                            {
                                var deviceId = Data.Utility.Guid(sttDevice, true).Value;
                                var session = new Data.Session<Data.AccessToken>(deviceId.ToString());
                                if (session.Value != null && session.Value.Device == deviceId)
                                {
                                    if (session.Value.IsInRole(UMC.Security.Membership.UserRole))
                                    {
                                        if (Security.AuthManager.Authorization(session.Value.Identity(), 0, new StringValue($"Device/{devices[1]}"), out var isBiometric) > 0)
                                        {
                                            if (isBiometric && session.Value.BiometricTime == 0)
                                            {
                                                var seesionKey = UMC.Data.Utility.Guid(session.Value.Device.Value);
                                                var url = $"/Biometric?oauth_callback={Uri.EscapeDataString(request.Url.AbsoluteUri)}&transfer={seesionKey}";
                                                webr.Send("{\"type\":\"url\",\"value\":\"" + url + "\"}");
                                            }
                                            else
                                            {
                                                var device = UMC.Data.HotCache.Get(new Entities.Device { Id = Utility.UInt64Parse(devices[1], 0) });
                                                if (device != null)
                                                {
                                                    switch (device.Type ?? Entities.DeviceType.Tcp)
                                                    {
                                                        case Entities.DeviceType.Ssh:
                                                            {
                                                                var password = UMC.Data.DataFactory.Instance().Password(SiteConfig.MD5Key(device.Id.ToString(), device.Username));

                                                                var ssh = new SshClient(device.Ip, device.Port ?? 22, device.Username, password);

                                                                var _QueryString = System.Web.HttpUtility.ParseQueryString(request.Url.Query);
                                                                webr.Send("{\"type\":\"device\",\"value\":\"" + Utility.Guid(dID) + "\"}");

                                                                new HttpShell(ssh, webr, device, _QueryString, session.Value.Username, this.RemoteIpAddress, request._UserAgent, request.Headers.Get(StringValue.Referer.Span));

                                                            }
                                                            return;
                                                        default:
                                                        case Entities.DeviceType.Tcp:
                                                            {
                                                                webr.Send("{\"type\":\"device\",\"value\":\"" + Utility.Guid(dID) + "\"}");
                                                                webr.Send("{\"type\":\"info\",\"value\":{\"Title\":\"" + device.Caption + "\",\"Key\":\"" + device.Id + "\"}}");

                                                                var sb = new StringBuilder();
                                                                sb.AppendLine("设备资源支持Socks5代理连接、本机端口映射和手机端口映射，三种方式连接此设备");
                                                                sb.AppendLine();
                                                                sb.AppendLine("一、Socks5代理连接");
                                                                sb.AppendLine("\t如果终端软件支持代理设置，则可以使用下例参数配置:");
                                                                // sb.AppendLine();
                                                                sb.Append("\t资源地址: ");
                                                                var port = BitConverter.ToUInt16(BitConverter.GetBytes(device.Id.Value).AsSpan(6, 2));
                                                                sb.AppendLine($"{device.Ip}:{port}");
                                                                // sb.Append(device.Type);
                                                                sb.Append("\t代理服务: ");
                                                                sb.AppendLine($"{request._host}:443");
                                                                sb.Append("\t代理账号: ");
                                                                sb.AppendLine($"{session.Value.Username}@{request._host}");
                                                                sb.Append("\t代理密码: ");
                                                                sb.AppendLine(session.Value.Username);
                                                                sb.AppendLine();
                                                                sb.AppendLine("二、本机端口映射");
                                                                sb.AppendLine("\t本机端口映射是用Socks5代理来映射成本机端口，让终端连接本机端口，从而连接到目标服务器。");
                                                                sb.AppendLine($"\t先要下载ITME，进入指令模式，再用 repost {device.Id}@{request._host} 来转发此资源");
                                                                sb.AppendLine($"\t然后再用浏览器以http://127.0.0.1:[转发端口]来打开，");
                                                                sb.AppendLine($"\t最后用ITME扫码并授权，则就可以使用127.0.0.1:[转发端口]连接服务了。");

                                                                sb.AppendLine();
                                                                sb.AppendLine("三、手机端口映射");
                                                                sb.AppendLine("\t先在手机上安装移动端ITME，并找到此资源，再点击映射，并保持手机与终端在同一局域网");
                                                                sb.AppendLine("\t则可以使用手机IP来让终端连接了，从而连接到目标服务器。");
                                                                // sb.ap
                                                                webr.Send(sb.ToString().UTF8());
                                                            }
                                                            break;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                }
                            }

                            HttpLogerDoWorker.AppendLog(request.netLoger);
                        }
                        else
                        {
                            OutText(403, String.Empty);
                        }
                    }
                    else
                    {
                        var context = new HttpMimeContext(request, this);
                        context.ProcessRequest();
                        this.WebSocket(context);

                        HttpLogerDoWorker.AppendLog(request.netLoger);
                    }
                }
                else
                {
                    var context = new HttpMimeContext(request, this);
                    context.ProcessRequest();
                    context.ProcessAfter();
                }

            }
            catch (Exception ex)
            {
                var ms = ex.ToString();
                request.netLoger.Error().Write("Server").Write(ms).End();
                OutText(500, ms);

            }


        }
        public virtual void WriteLog(long startTime, HttpLoger loger)
        {
            loger.Write(HttpLogerType.Duration, (int)(UMC.Data.Reflection.TimeSpanMilli(DateTime.Now) - startTime));

            HttpLogerDoWorker.AppendLog(loger);
        }
        public virtual void OutText(int status, string contentType, String text)
        {

            if (this.IsWrited == false)
            {
                var writer = new TextWriter(this.Send);
                writer.Write($"HTTP/1.1 {status} {HttpStatusDescription.Get(status)}\r\n");
                writer.Write($"Content-Type: {contentType}; charset=utf-8\r\n");
                writer.Write($"Content-Length: {System.Text.Encoding.UTF8.GetByteCount(text)}\r\n");
                writer.Write("Connection: close\r\n");
                writer.Write("Server: ITME\r\n\r\n");
                writer.Write(text);
                writer.Flush();
                writer.Close();

            }
            if (this.Request is HttpMimeRequest)
            {
                var h = this.Request as HttpMimeRequest;
                h.netLoger.Write(HttpLogerType.Status, status);
                HttpLogerDoWorker.AppendLog(h.netLoger);
            }

            this.Stop();

        }
        public void Challenge(StringValue host)
        {
            Challenge(host, 401, false);
        }

        public void Challenge(StringValue host, int statecode, bool isClearCookie)
        {

            Challenge(host, statecode, isClearCookie, "1.0");
        }
        public virtual void Challenge(StringValue host, int statecode, bool isClearCookie, string version)
        {
            // String lang;
            // if (this.Request.Headers.TryGet(StringValue.AcceptLanguage.Span, out var headValue))
            // {
            //     lang = $"{headValue.Value.Slice(","u8).UTF8()}.js";
            // }
            // else
            // {
            //     lang = "zh-CN.js";
            // }
            // if (this.Request is HttpMimeRequest)
            // {
            //     var h = this.Request as HttpMimeRequest;
            //    // h.c
            //     HttpsMimeSocket.Challenge(host, statecode, isClearCookie, version, this.RemoteIpAddress, lang, this.tlsSite1, this.Send);


            //     h.netLoger.Write(HttpLogerType.Status, statecode);
            //     HttpLogerDoWorker.AppendLog(h.netLoger);
            // }
            // this.Dispose();

        }
        public void OutText(int status)
        {
            this.OutText(status, String.Empty);
        }
        public virtual void CheckTimeOut()
        {
            if (this.IsWrited)
            {
                this.Dispose();
            }
            else
            {

                this.OutText(504, "Gateway Timeout");
            }
        }
        public void OutText(int status, String text)
        {
            var sb = new StringBuilder();
            sb.AppendLine("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">");
            sb.AppendLine("");
            sb.AppendLine(@"<html>");
            sb.AppendFormat("<head><title>{0} {1}</title></head>", status, HttpStatusDescription.Get(status));
            sb.AppendLine();
            sb.AppendLine("<body>");
            sb.AppendFormat("<center><h1>{0} {1}</h1></center>", status, HttpStatusDescription.Get(status));
            if (String.IsNullOrEmpty(text) == false)
                sb.AppendLine($"<pre>{text}</pre>");
            sb.AppendLine();
            sb.AppendLine("<hr/>Powered by ITME<hr><center>ITME</center>");
            sb.AppendLine("</body>");
            sb.AppendLine("</html>");

            this.OutText(status, "text/html", sb.ToString());

        }
    }
}

